//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#include "NjNinjaSync.h"
#include "NjEvent.h"

//----------------------------------------------------------------------------
#if NJ_EVENT_TRACKING_ENABLED
#	include "NjDebugLocklessCircularBuffer.h"

	class NjEvent::TrackerEntry
	{
	public:
		NJ_TRIVIAL_DEFAULT_CONSTRUCTOR(TrackerEntry);
		NJ_TRIVIAL_DESTRUCTOR(TrackerEntry);

		NjUint m_uThreadDebugId;
		NjUint m_uLine;
		NjEvent const * m_pEvent;

		static const NjUint BUFFER_SIZE = 1024;

		NJ_DISABLE_COPY(TrackerEntry);
	};
	//----------------------------------------------------------------------------
	class NjEvent::Tracker
	{
	public:
		static void Record(NjEvent const * pEvent, NjUint uThreadDebugId, NjUint uLine);

	private:
		typedef NjDebugLocklessCircularBuffer<NjEvent::TrackerEntry, NjEvent::TrackerEntry::BUFFER_SIZE> CircularBuffer;
		static CircularBuffer ms_kCircularBuffer;

		NJ_DISABLE_INSTANCE(Tracker);
	};

#	define NJ_EVENT_TRACKING() Tracker::Record(this, uThreadDebugId, __LINE__)
#else // NJ_EVENT_TRACKING_ENABLED
#	define NJ_EVENT_TRACKING()
#endif // NJ_EVENT_TRACKING_ENABLED
//----------------------------------------------------------------------------
#if NJ_EVENT_STORE_USERS_ENABLED
#	define NJ_EVENT_USER_ENTER() UserEnter()
#	define NJ_EVENT_USER_LEAVE() UserLeave()
#else // NJ_EVENT_STORE_USERS_ENABLED
#	define NJ_EVENT_USER_ENTER()
#	define NJ_EVENT_USER_LEAVE()
#endif // NJ_EVENT_STORE_USERS_ENABLED
//----------------------------------------------------------------------------
NjEvent::~NjEvent()
{
#if NJ_EVENT_STORE_USERS_ENABLED
	NJ_ASSERT(m_kUsers.Get()==0);
#endif // NJ_EVENT_STORE_USERS_ENABLED

	Event * const pEvent(m_kEvent.Get());

	if (pEvent==NJ_NULL)
		return;

	NjEventsSystem::DestroyEvent(pEvent);
}
//----------------------------------------------------------------------------
void NjEvent::Unset()
{
	NJ_EVENT_USER_ENTER();

#if NJ_EVENT_TRACKING_ENABLED
	NjUint uThreadDebugId;
	if (!NjEventsSystem::GetCurrentThreadDebugId(uThreadDebugId))
	{
		NJ_BREAK();
		uThreadDebugId=NjEventsSystem::INVALID_THREAD_ID;
	}
#endif // NJ_EVENT_TRACKING_ENABLED

	NJ_EVENT_TRACKING();
	m_kState.PostOr(STATE_USER_EVENT_UNSET);
	NJ_EVENT_USER_LEAVE();
}
//----------------------------------------------------------------------------
void NjEvent::Set()
{
	NJ_EVENT_USER_ENTER();

#if NJ_EVENT_TRACKING_ENABLED
	NjUint uThreadDebugId;
	if (!NjEventsSystem::GetCurrentThreadDebugId(uThreadDebugId))
	{
		NJ_BREAK();
		uThreadDebugId=NjEventsSystem::INVALID_THREAD_ID;
	}
#endif // NJ_EVENT_TRACKING_ENABLED

	NJ_EVENT_TRACKING();

	NjUint uState(m_kState.PostAnd(~STATE_USER_EVENT_UNSET)&(~STATE_USER_EVENT_UNSET));

	if ((uState&STATE_KERNEL_EVENT_UNSET)!=0)
	{
		Event * pEvent(m_kEvent.Get());
		NJ_ASSERT(pEvent!=NJ_NULL);

		for(;;)
		{
#if NJ_ASSERT_ENABLED
			NjBool const bResult=
#endif // NJ_ASSERT_ENABLED

			NjEventsSystem::SetEvent(pEvent);

			NJ_ASSERT(bResult);

			NjUint const uNewState(uState+STATE_ABA_COUNTER_ONE-STATE_KERNEL_EVENT_UNSET);
			NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

			if (uOldState==uState ||
				(uOldState&STATE_KERNEL_EVENT_UNSET)==0)
				break;

			uState=uOldState;
		}
	}

	NJ_EVENT_TRACKING();
	NJ_EVENT_USER_LEAVE();
}
//----------------------------------------------------------------------------
NjBool NjEvent::TryWait(NjUint uTimeMs)
{
	NJ_EVENT_USER_ENTER();

#if NJ_EVENT_TRACKING_ENABLED
	NjUint uThreadDebugId;
	if (!NjEventsSystem::GetCurrentThreadDebugId(uThreadDebugId))
	{
		NJ_BREAK();
		uThreadDebugId=NjEventsSystem::INVALID_THREAD_ID;
	}
#endif // NJ_EVENT_TRACKING_ENABLED

	NJ_EVENT_TRACKING();

	NjUint uSpinCount(NJ_MAX_UINT);
	TimeMs uStart(0);

	for(;;)
	{
		NjUint uState(m_kState.Get());

		if ((uState&STATE_USER_EVENT_UNSET)==0)
			break;

		if (uTimeMs==0)
		{
			NJ_EVENT_TRACKING();
			NJ_EVENT_USER_LEAVE();
			return NJ_FALSE;
		}

		if (uSpinCount==NJ_MAX_UINT)
		{
			if (!NjEventsSystem::GetTimeMs(uStart))
			{
				NJ_BREAK();
				NJ_EVENT_TRACKING();
				NJ_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			if (!NjEventsSystem::GetSpinCount(uSpinCount))
			{
				NJ_BREAK();
				NJ_EVENT_TRACKING();
				NJ_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			NJ_ASSERT(uSpinCount>0);
			--uSpinCount;
		}

		if (uSpinCount>0)
		{
			NjUint uIterations(uSpinCount);

			do
			{
				uState=m_kState.Get();

				if ((uState&STATE_USER_EVENT_UNSET)==0)
					break;

				--uIterations;
			}
			while (uIterations>0);

			if (uIterations>0)
				break;
		}

		Event * pEvent(m_kEvent.Get());

		if (pEvent==NJ_NULL)
		{
			NJ_ASSERT((uState&STATE_KERNEL_EVENT_UNSET)==0);
			pEvent=NjEventsSystem::CreateEvent(NJ_FALSE);

			if (pEvent==NJ_NULL)
			{
				NJ_BREAK();
				NJ_EVENT_TRACKING();
				NJ_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			Event * const pOldEvent(m_kEvent.PostCompareExchange(NJ_NULL, pEvent));
			if (pOldEvent!=NJ_NULL)
			{
				if (!NjEventsSystem::DestroyEvent(pEvent))
				{
					NJ_BREAK();
					NJ_EVENT_TRACKING();
					NJ_EVENT_USER_LEAVE();
					return NJ_FALSE;
				}

				pEvent=pOldEvent;
			}
			else
			{
				NjUint const uNewState(uState+STATE_ABA_COUNTER_ONE+STATE_KERNEL_EVENT_UNSET);
				NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

				if (uOldState==uState)
					uState|=STATE_KERNEL_EVENT_UNSET;
				else
					uState=uOldState;
			}
		}

		if ((uState&STATE_KERNEL_EVENT_UNSET)==0)
		{
			for(;;)
			{
				if (!NjEventsSystem::UnsetEvent(pEvent))
				{
					NJ_BREAK();
					NJ_EVENT_TRACKING();
					NJ_EVENT_USER_LEAVE();
					return NJ_FALSE;
				}

				NjUint const uNewState(uState+STATE_ABA_COUNTER_ONE+STATE_KERNEL_EVENT_UNSET);
				NjUint const uOldState(m_kState.PostCompareExchange(uState, uNewState));

				if (uOldState==uState)
					break;

				if ((uOldState&STATE_KERNEL_EVENT_UNSET)!=0)
				{
					uState=uOldState;
					break;
				}

				uState=uOldState;
			}

			if ((uState&STATE_USER_EVENT_UNSET)==0)
				break;
		}

		NjEventsSystem::TryResult const eTryResult(NjEventsSystem::TryWaitEvent(pEvent, uTimeMs));

		if (eTryResult!=NjEventsSystem::TRY_SUCCESS)
		{
			NJ_ASSERT(eTryResult==NjEventsSystem::TRY_TIMEOUT && uTimeMs!=NjEventsSystem::WAIT_FOREVER);
			NJ_EVENT_TRACKING();
			NJ_EVENT_USER_LEAVE();
			return NJ_FALSE;
		}

		if (uTimeMs!=NjEventsSystem::WAIT_FOREVER)
		{
			TimeMs uEnd(0);

			if (!NjEventsSystem::GetTimeMs(uEnd))
			{
				NJ_BREAK();
				NJ_EVENT_TRACKING();
				NJ_EVENT_USER_LEAVE();
				return NJ_FALSE;
			}

			NJ_ASSERT(uEnd>=uStart);
			uEnd-=uStart;
			uStart+=uEnd;

			if (uEnd>=uTimeMs)
				uTimeMs=0;
			else
				uTimeMs-=uEnd;
		}
	}

	NJ_EVENT_TRACKING();
	NJ_EVENT_USER_LEAVE();
	return NJ_TRUE;
}
//----------------------------------------------------------------------------
#if NJ_EVENT_STORE_USERS_ENABLED
	inline void NjEvent::UserEnter()
	{
#	if NJ_ASSERT_ENABLED
		NjUint uTotUsers=
#	endif // NJ_ASSERT_ENABLED

		m_kUsers.PreIncrement();

		NJ_ASSERT(uTotUsers>0);
	}
	//----------------------------------------------------------------------------
	inline void NjEvent::UserLeave()
	{
#	if NJ_ASSERT_ENABLED
		NjUint uTotUsers=
#	endif // NJ_ASSERT_ENABLED

		m_kUsers.PostDecrement();

		NJ_ASSERT(uTotUsers>0);
	}
#endif // NJ_EVENT_STORE_USERS_ENABLED
//----------------------------------------------------------------------------
#if NJ_EVENT_TRACKING_ENABLED
	void NjEvent::Tracker::Record(NjEvent const * pEvent, NjUint uThreadDebugId, NjUint uLine)
	{
		TrackerEntry * const pEntry(ms_kCircularBuffer.Record());

		pEntry->m_pEvent=pEvent;
		pEntry->m_uThreadDebugId=uThreadDebugId;
		pEntry->m_uLine=uLine;
	}
	//----------------------------------------------------------------------------
	NjEvent::Tracker::CircularBuffer NjEvent::Tracker::ms_kCircularBuffer;
#endif // NJ_EVENT_TRACKING_ENABLED
//----------------------------------------------------------------------------
